#include"include.h"

char crlf[] = {0xD, 0xA, '\0'}; // CRLF
char crlfcrlf[] = {0xD, 0xA, 0xD, 0xA, '\0'}; // Double CRLF

httpresp_t respdef = HTTPRESP_DEFAULT;

char *parseUri(char *hostdata)
{
   int ndx = 0;
   for(;hostdata[ndx] != '/';ndx++);
   return mysubstr(hostdata, ndx, strlen(hostdata) - ndx);
}

// Return buff should be at least same size as hostdata
void parseHostName(char *hostdata, char *returnBuff)
{
   while(*hostdata != ':'&& *hostdata != '/' && *hostdata != '\0')
   {
      *returnBuff = *hostdata;
      returnBuff++;
      hostdata++;
   }
   *returnBuff = 0;
}

// Gets port from host[: port], 80 by default
unsigned short parsePort(char *hostdata)
{
   unsigned short port;
   while(*hostdata != ':' && *hostdata != '\0')
      hostdata++;
   // If no port specified, return default http port 80
   if(*hostdata == '\0' || *hostdata == '/')  //tracker.torrent.com:3400/announce
      return 80;
   hostdata++;
   for(port = 0;isInt(*hostdata);hostdata++)
   {
      port *= 10;
      port += *hostdata - '0';
   }
   return port;
}

// Adds parameters from a dictionary to a URI path
mystr *addparams(mystr *rReq, dict_t *params)
{
   if(params != NULL)
   {
      list_t *parms = dictTraversal(params);
      dict_key_val_t *entry;

      resetList(parms);
      if(!endList(parms))
      {
         entry = getObject(parms);
         mystrcats(rReq, "?");
         mystrcats(rReq, entry->key);
         mystrcats(rReq, "=");
         mystrcats(rReq, entry->val);
      }
      while(!endList(parms))
      {
         entry = getObject(parms);
         mystrcats(rReq, "&");
         mystrcats(rReq, entry->key);
         mystrcats(rReq, "=");
         mystrcats(rReq, entry->val);
      }
      // Clean created list
      clearListAndFreeElements(parms);
   }
   return rReq;
}

// Adds headers from a dictionary to an HTTP request
mystr *addheaders(mystr *rReq, dict_t *headers)
{
   if(headers != NULL)
   {
      list_t *hdrs = dictTraversal(headers);
      dict_key_val_t *entry;

      resetList(hdrs);
      while(!endList(hdrs))
      {
         entry = getObject(hdrs);
         mystrcats(rReq, entry->key);
         mystrcats(rReq, ": ");
         mystrcats(rReq, entry->val);
         mystrcats(rReq, crlf);
      }
      // Clean created list
      clearListAndFreeElements(hdrs);
   }
   return rReq;
}

// Turns an httpreq_t into a raw request
mystr *makeHTTPRequestData(httpreq_t req)
{
   // Construct memory for raw request
   char *rawReq = malloc(101);
   memset(rawReq, '\0', 101);
   mystr *rReq = malloc(sizeof(mystr));
   rReq->s = rawReq; 
   rReq->len = 0;
   rReq->maxlen = 100;
   
   // Make Request Line
   mystrcats(rReq, req.method); // Request method
   mystrcats(rReq, " ");
   mystrcats(rReq, req.reqURI); // URI Path
   rReq = addparams(rReq, req.parameters); // Parameters
   mystrcats(rReq, " ");
   mystrcats(rReq, req.protoVersion);
   mystrcats(rReq, crlf);

   // Add Headers
   addheaders(rReq, req.headers);

   // If there aren't any headers add another crlf
   if (req.headers == NULL);
      mystrcats(rReq, crlf);

   mystrcats(rReq, crlf);
 
   // Add Body
   // NOT IMPLEMENTED

   return rReq;
}

// Parses the status line of an HTTP response
void parseStatusLine(char *data, httpresp_t *response)
{
   int len = strstr(data, crlf) - data;
   response->protoVersion = malloc(len);
   sscanf(data, "%s %d", response->protoVersion, &(response->statusCode));
}

// Parses headers from an HTTP response until CRLFCRLF is read
void parseHeaders(char *data, httpresp_t *response, char **msgstart)
{
   char *nextPlace;
   char *curr;
   int len;
   char *key;
   char *val;
   dict_t *hdrs = NULL;
   *msgstart = data + 2;
   if(strstr(data, crlf) != data)
   {
      hdrs = newDict((int (*)(const void *, const void *))strcmp);

      // While the next string is not crlf
      while((nextPlace = strstr(data, crlf)) != data)
      {
         len = nextPlace - data;
         nextPlace += 2;

         key = malloc(len);
         val = malloc(len);

         // Parse Key->Value
         curr = strtok(data, ":\r\n");
         sscanf(curr, "%s", key);
         curr = strtok(NULL, ":\r\n");
         sscanf(curr, "%s", val);

         dictPut(hdrs, key, val);

         data = nextPlace;
      }
      *msgstart = nextPlace + 2;
   }
   response->headers = hdrs;
}

// Turns an http response into an httpresp_t
httpresp_t constructHTTPResponseData(unsigned char *idata, int len)
{
   httpresp_t ret = HTTPRESP_DEFAULT;

   char *data = (char *) idata;
   char *respData;

   // Parse data for http response
   // Parse Status-Line
   parseStatusLine(data, &ret);
   data = strstr(data, crlf) + 2;

   // Parse Headers
   parseHeaders(data, &ret, &respData);

   // Find data
   int datalen = (data + len) - respData;
   if(datalen > 0)
   {  // If there is returned data...
      ret.body = malloc(datalen);
      memcpy(ret.body, respData, datalen);
      ret.bodylen = datalen;
   }

   return ret;
}

httpresp_t make_request(httpreq_t req)
{
   httpresp_t response = HTTPRESP_DEFAULT;


   int sock;                    // Socket descriptor
   struct sockaddr_in servAddr; // HTTP Server
   //struct sockaddr_in fromAddr; // Source address
   struct hostent *servHost;    // Hostent from gethostbyname()

   char *servIP;                // Server host name or IP
   unsigned short servPort;     // Port to connect to server on

   // Construct Request string to be sent
   mystr *request = makeHTTPRequestData(req);

   // Unpack the server IP and port from the httpreq_t
   char *shost = dictGet(req.headers, "Host");
   int hostnamelen = strlen(shost) + 1;
   char shostname[hostnamelen];   // Buffer to hold host name
   memset(shostname, '\0', hostnamelen);
   parseHostName(shost, shostname); // Parse host name
   
   servIP = shostname;
   servPort = parsePort(shost);     // Parse port

   // Create TCP Socket
   if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
   {
      loggerLog(LOG_ERR, "Could not create tcp socket.\n");
      response.error = 1;
      return response;
   }

   // Construct HTTP Server Address Structure
   memset(&servAddr, 0, sizeof(servAddr));
   servAddr.sin_family      = AF_INET;
   servAddr.sin_addr.s_addr = inet_addr(servIP);
   servAddr.sin_port        = htons(servPort);
   // Resolve host name given
   if(servAddr.sin_addr.s_addr == -1)
   {
      servHost = gethostbyname(servIP);
      if(servHost == 0)
      {
         loggerLog(LOG_ERR, "Could not resolve tracker name\n");
	 response.error = 1;
	 return response;
      }
      servAddr.sin_addr.s_addr = *((unsigned long *) servHost->h_addr_list[0]);
   }

   // Connect to HTTP server
   if(connect(sock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0)
   {
      loggerLog(LOG_ERR,"Could not connect to server.\n");
      response.error = 1;
      return response;
   }

   // Send HTTP Request
   int slen = (request->len);

   if(send(sock, request->s, slen, 0) != slen)
   {
      loggerLog(LOG_ERR, "Could not send data via http.\n");
      response.error = 1;
      return response;
   }

   // Receive HTTP Response
   // Received info metadata
   int recvd = 0;
   int bufflen = 0;
   int reincSize = 1024;
   int maxlen = 1024;
   unsigned char *recvBuff = malloc(1024);

   struct pollfd sockPoll = {sock, POLLIN | POLLPRI, 0};
   int timeouts = 0;
   int pollRet = 0;

   while(timeouts < MAX_TIMEOUTS)
   {  // Check for data
      if((pollRet = poll(&sockPoll, 1, RESP_WAIT_TIME)) <= 0)
         timeouts++;
      else
      { // There's data to read
         timeouts = MAX_TIMEOUTS - 1; // Set timeouts for fast final timeout
         if((recvd = recv(sock, recvBuff + bufflen, reincSize, 0)) < 0)
         {  // Error receiving data from socket
            loggerLog(LOG_ERR, "Error receiving data from socket\n");
            response.error = 1;
            return response;
         }
         else if(recvd > 0)
         {
            bufflen += recvd; // Increment buffer's data counter
            maxlen += reincSize;
            recvBuff = realloc(recvBuff, maxlen);
         }
         else
            break;
      }
   }
   if(bufflen == 0 && timeouts == MAX_TIMEOUTS)
   { // Connection timed out without receiving data
      loggerLog(LOG_ERR, "HTTP server timeout.\n");
      response.error = 1;
      return response;
   }
   

   if(bufflen != 0)
   { // Construct HTTP Response
      response = constructHTTPResponseData(recvBuff, bufflen);
   }

   // Cleanup
   close(sock);

   // Free allocated string
   free(request->s);
   free(request);
   free(recvBuff);

   return response;
}

mystr *httpGetUrlBody(char *url, dict_t *params)
{
   char *purl = url;
   if(mystrStartsWith(url, "http://"))
      purl += 7;

   httpreq_t req = HTTPREQ_DEFAULT;
   
   int hostnamelen = strlen(purl) + 1;
   char shostname[hostnamelen];   // Buffer to hold host name
   memset(shostname, '\0', hostnamelen);
   parseHostName(purl, shostname); // Parse host name



   dict_t *headers = newDict( (int (*)(const void *, const void *))strcmp);
   dictPut(headers, "Host", purl);
   req.parameters = params;
   req.method = "GET";
   req.reqURI = parseUri(purl);
   req.headers = headers;

  /* mystr *request = makeHTTPRequestData(req);
   printf("Making request: %s\n", request->s); */


   httpresp_t resp = make_request(req);
   // Cleanup the httpresp_t
   dictClearKeysVals(resp.headers);
   free(resp.headers);
   free(resp.protoVersion);

   mystr *res = malloc(sizeof(mystr));
   res->s = resp.body;
   res->len = resp.bodylen;
   return res;
}

void httprespFree(httpresp_t *resp)
{
   free(resp->protoVersion);
   free(resp->body);
   dictClear(resp->headers);
   free(resp->headers);

   *resp = respdef;
}
